package main

import (
	"fmt"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

const JWT_API_KEY_APP string = "JWT_API_KEY_APP"
const JWT_API_KEY_USER string = "JWT_API_KEY_USER"

const API_TIMESTAMP_EXPIRE int = 7200

func api_middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		path := c.Request.URL.Path
		timestamp := c.Query("timestamp")
		appid := c.Query("appid")
		sign := c.Query("sign")

		if sign == "" || timestamp == "" {
			logger.Warningf("empty sign or empty timestamp.")
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		stamp, err := strconv.Atoi(timestamp)
		if err != nil {
			logger.Warningf("timestamp must be digit.")
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		cur := time.Now().Unix()
		if math.Abs(float64(cur)-float64(stamp)) > float64(API_TIMESTAMP_EXPIRE) {
			logger.Warningf("cur %d, url %d", cur, stamp)
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		appdbid, err := strconv.Atoi(appid)
		if err != nil {
			logger.Warningf("appid is not digit, ignore.")
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		app, err := AppDetailInfo(appdbid)
		if err != nil {
			logger.Warningf("app cannot found %d", appdbid)
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		hashSrc := strings.ToLower(fmt.Sprintf("%s%sappid=%stimestamp=%s%s",
			method, path, appid, timestamp, app.AppKey))
		hashNow := md5str(hashSrc)
		if strings.ToLower(hashNow) != strings.ToLower(sign) {
			logger.Warningf("[%s] URL: %s, MINE: %s", hashSrc, sign, hashNow)
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		user, err := FetchUserFromAppID(appdbid)
		if err != nil {
			logger.Warningf("user cannot found on app %d", appdbid)
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		c.Set(JWT_API_KEY_APP, app)
		c.Set(JWT_API_KEY_USER, user)
		//processing
		c.Next()

		//after
	}
}

func AppDetailInfo(appdbid int) (app App, err error) {
	query := gldb.Where("id=? and stat='VALID'", appdbid).First(&app)
	if query.Error != nil {
		logger.Warningf("cannot found appid")
		err = query.Error
		return
	}
	return
}

func FetchUserFromAppID(appdbid int) (user User, err error) {
	query := gldb.Joins("join apps on apps.user_id=users.id")
	query = query.Where("apps.stat='VALID' and users.stat='VALID' and apps.id=?", appdbid)
	query = query.First(&user)
	if query.Error != nil {
		logger.Warningf("cannot found user from appdbid %d", appdbid)
		err = query.Error
		return
	}
	return
}

func DevicesListApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)

	devices := []Device{}
	query := gldb.Where("app_id=? and stat='VALID'", app.ID).Find(&devices)
	if query.Error != nil {
		logger.Warningf("app %d query failed.", app.ID)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	result := []uint{}
	for _, dev := range devices {
		result = append(result, dev.ID)
	}
	c.JSON(http.StatusOK, result)
}

type ApiRspDevice struct {
	DeviceDbID uint   `json:"id"`
	Name       string `json:"name"`
	ChipID     string `json:"imei"`
	Latest     string `json:"latest"`
}

func AddDeviceApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	var jsonreq struct {
		ChipID string `json:"imei" binding:"required"`
		Name   string `json:"name" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}

	cur := time.Now()
	device := Device{
		AppID:   app.ID,
		ChipId:  jsonreq.ChipID,
		Name:    jsonreq.Name,
		Latest:  cur,
		DevUuid: NewDeviceUUID(),
		Stat:    "VALID",
	}

	query := gldb.Create(&device)
	if query.Error != nil {
		logger.Warningf("add device %S FAILED", jsonreq.ChipID)
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	res := ApiRspDevice{
		DeviceDbID: device.ID,
		ChipID:     jsonreq.ChipID,
		Name:       jsonreq.Name,
		Latest:     TimeToString(cur),
	}
	c.JSON(http.StatusOK, res)
}

func FetchDeviceDetailFromID(devdbid uint, appdbid uint) (device Device, err error) {
	query := gldb.Where("id=? and stat='VALID' and app_id=?", devdbid, appdbid).First(&device)
	if query.Error != nil {
		logger.Warningf("cannot found special device %d", devdbid)
		err = query.Error
		return
	}
	return
}

func DeviceDetailApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	devdbid := c.Param("devdbid")
	if devdbid == "" {
		logger.Warningf("device dbid empty.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	devid, err := strconv.Atoi(devdbid)
	if err != nil {
		logger.Warningf("devdbid must be digit.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	device, err := FetchDeviceDetailFromID(uint(devid), app.ID)
	if err != nil {
		logger.Warningf("fetch device from id fail")
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	c.JSON(http.StatusOK, ApiRspDevice{
		DeviceDbID: device.ID,
		Name:       device.Name,
		ChipID:     device.ChipId,
		Latest:     TimeToString(device.Latest),
	})
}

func DeviceModifyApiHandler(c *gin.Context) {
	//
}

func RemoveDeviceApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	devdbid, err := strconv.Atoi(c.Param("devdbid"))
	if err != nil {
		logger.Warningf("device dbid format error.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	var device Device
	query := gldb.Where("id=? and stat='VALID' and app_id=?", devdbid, app.ID).First(&device)
	if query.Error != nil {
		logger.Warningf("cannot found special device %d", devdbid)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	device.Stat = "DELETED"
	gldb.Save(&device)
	gldb.Delete(&device)
	c.JSON(http.StatusOK, gin.H{"msg": "remove succ."})
}

/*
	gorm.Model
	Device    Device `gorm:"ForeignKey:DeviceID"`
	DeviceID  int
	Msgid     uint
	Direction uint
	Token     string `gorm:"size:16"`
	Data      string `gorm:"type:text"`
	Stat      string `gorm:"size:16"`
*/
func FetchDevicesByAppID(appdbid uint) (devices []uint, err error) {
	rows, err := gldb.Table("devices").Select("id").Where("stat='VALID' and app_id=?", appdbid).Rows()
	if err != nil {
		logger.Warningf("query devices from appid failed.")
		return
	}
	defer rows.Close()

	for rows.Next() {
		var devdbid uint
		rows.Scan(&devdbid)
		devices = append(devices, devdbid)
	}
	return
}

func FetchMessagesByDeviceID(devdbid uint) (messages []CoapMessage, err error) {
	query := gldb.Where("device_id=?", devdbid).Find(&messages)
	if query.Error != nil {
		logger.Warningf("query messages by devdbid failed.")
		err = query.Error
		return
	}
	return
}

type ApiRspMessage struct {
	DeviceDbID uint   `json:"device_id"`
	MsgID      uint   `json:"id"`
	Direction  uint   `json:"direction"`
	Session    string `json:"session"`
	Data       string `json:"data"`
}

func MessagesListApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	devdbid, err := strconv.Atoi(c.Param("devdbid"))
	if err != nil {
		logger.Warningf("device dbid format error.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	_, err = FetchDeviceDetailFromID(uint(devdbid), uint(app.ID))
	if err != nil {
		logger.Warningf("no permission for device")
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	msgs, err := FetchMessagesByDeviceID(uint(devdbid))
	if err != nil {
		logger.Warningf("messages list api failed. %d", devdbid)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	results := []ApiRspMessage{}
	for _, msg := range msgs {
		results = append(results, ApiRspMessage{
			DeviceDbID: uint(msg.DeviceID),
			MsgID:      msg.ID,
			Direction:  msg.Direction,
			Session:    msg.Token,
			Data:       msg.Data,
		})
	}
	c.JSON(http.StatusOK, results)
}

func AddMessageApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	var jsonreq struct {
		Data string `json:"data" binding:"required"`
		Soon string `json:"soon" binding:"required"`
	}
	err := c.BindJSON(&jsonreq)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "bad request."})
		return
	}

	devdbid, err := strconv.Atoi(c.Param("devdbid"))
	if err != nil {
		logger.Warningf("device dbid format error.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	device, err := FetchDeviceDetailFromID(uint(devdbid), uint(app.ID))
	if err != nil {
		logger.Warningf("no permission to device")
		c.AbortWithStatus(http.StatusNotFound)
		return
	}
	logger.Infof("got device %d", device.ID)
}

func FetchMsgFromID(msgdbid uint, appdbid uint) (msg CoapMessage, err error) {
	query := gldb.Joins("devices on devices.id=coap_messages.device_id")
	query = query.Joins("apps on apps.id=devices.app_id")
	query = query.Where("devices.stat='VALID' and apps.stat='VALID'")
	query = query.Where("coap_messages.id = ? and apps.id = ?", msgdbid, appdbid)
	query = query.First(&msg)
	if query.Error != nil {
		err = query.Error
		return
	}
	return
}

func MessageDetailApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	msgdbid := c.Param("msgdbid")
	msgid, err := strconv.Atoi(msgdbid)
	if err != nil {
		logger.Warningf("msgdbid must be digit, %s", msgdbid)
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	msg, err := FetchMsgFromID(uint(msgid), app.ID)
	if err != nil {
		logger.Warningf("fetch msg from id failed. %d, app: %d", msgid, app.ID)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	device, err := FetchDeviceDetailFromID(uint(msg.DeviceID), app.ID)
	if err != nil {
		logger.Warningf("msg device query failed.")
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	/*
		DeviceDbID uint   `json:"device"`
		MsgID      uint   `json:"id"`
		Direction  uint   `json:"direction"`
		Session    string `json:"session"`
		Data       string `json:"data"`
	*/
	c.JSON(http.StatusOK, ApiRspMessage{
		DeviceDbID: device.ID,
		MsgID:      msg.ID,
		Direction:  msg.Direction,
		Session:    msg.Token,
		Data:       msg.Data,
	})
}

func RemoveMessageApiHandler(c *gin.Context) {
	app := c.MustGet(JWT_API_KEY_APP).(App)
	msgdbid := c.Param("msgdbid")
	msgid, err := strconv.Atoi(msgdbid)
	if err != nil {
		logger.Warningf("msgdbid must be digit, %s", msgdbid)
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	msg, err := FetchMsgFromID(uint(msgid), app.ID)
	if err != nil {
		logger.Warningf("fetch msg from id failed. %d, app: %d", msgid, app.ID)
		c.AbortWithStatus(http.StatusNotFound)
		return
	}

	if msg.Stat != "PLANING" {
		logger.Warningf("message stat cannot remove.")
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	msg.Stat = "DELETED"
	gldb.Save(&msg)
	gldb.Delete(&msg)
	c.JSON(http.StatusOK, gin.H{"msg": "remove message ok."})
}

func v3apiInitialize(router *gin.Engine) {
	api_v3 := router.Group("/api/v3")
	api_v3.Use(api_middleware())
	{
		//websocket api
		WsApiInitialize()
		//		api_v3.GET("/peer", WebSocketApiHandler)
		//		//devices
		//		api_v3.GET("/devices", DevicesListApiHandler)
		//		api_v3.POST("/devices", AddDeviceApiHandler)
		//		//device detail.
		//		api_v3.GET("/devices/:devdbid", DeviceDetailApiHandler)
		//		api_v3.PUT("/devices/:devdbid", DeviceModifyApiHandler)
		//		api_v3.DELETE("/devices/:devdbid", RemoveDeviceApiHandler)
		//		//messages
		//		api_v3.GET("/devices/:devdbid/messages", MessagesListApiHandler)
		//		api_v3.POST("/devices/:devdbid/messages", AddMessageApiHandler)
		//		//message detail
		//		api_v3.GET("/messages/:msgdbid", MessageDetailApiHandler)
		//		api_v3.DELETE("/messages/:msgdbid", RemoveMessageApiHandler)
	}
}
